package org.pearl.springboot.minio.controller;

import com.google.common.collect.HashMultimap;
import io.minio.CreateMultipartUploadResponse;
import io.minio.ListPartsResponse;
import io.minio.messages.Part;
import lombok.SneakyThrows;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.pearl.springboot.minio.boot.MinioTemplate;
import org.pearl.springboot.minio.boot.OssFile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;

/**
 * Created by TD on 2021/7/8
 */
@Controller
@RequestMapping("/file")
public class FileController {

/*    @Autowired
    MinioTemplate minioTemplate;

    @PostMapping("/upload")
    public Object upload(MultipartFile file) throws IOException {
        minioTemplate.putFile(file.getName()+ UUID.randomUUID().toString(),file.getInputStream());
        return "success";
    }*/

    @Autowired
    MinioTemplate minioTemplate;


    @PostMapping("/chunk")
    @ResponseBody
    public Object chunk(MultipartFile file, String bucketName, String chunks, String chunk) throws IOException {
        return minioTemplate.putObject(file.getInputStream(), bucketName, file.getOriginalFilename());
    }


    @RequestMapping("/upload")
    @ResponseBody
    public Object upload(MultipartFile file, String bucketName, HttpServletRequest request) throws IOException {
        // 分片上传时，会发送当前所有分片数量chunks ，当前分片数chunk
        Map<String, String[]> parameterMap = request.getParameterMap();
        System.out.println(parameterMap.toString());

        // MD5 秒传
        // 1. 获取到该文件的MD5
        String md5 = request.getParameter("uniqueIdentifier");
        // 2. 判断该MD5 是否已存在
        boolean contains = md5Set.contains(md5);
        // 3. 已存在直接返回访问路径
        if (contains) {
            return "该文件已上传，链接为：" + "http:xxxxxxxxxxxx";
        } else {
            // 4. 不存在则执行上传并保存MD5 记录到数据库
            OssFile ossFile = minioTemplate.putObject(file.getInputStream(), bucketName, file.getOriginalFilename());
            md5Set.add(md5);
            md5Set.forEach(e -> {
                System.err.println("已存在的MD5：" + e);
            });
            return ossFile;
        }
    }


    @RequestMapping("/ChunkUploadUrl")
    @ResponseBody
    public Object ChunkUploadUrl(MultipartFile file, String bucketName, HttpServletRequest request) throws IOException {
        // 分片上传时，会发送当前所有分片数量chunks ，当前分片数chunk
        Map<String, String[]> parameterMap = request.getParameterMap();
        System.out.println(parameterMap.toString());

        // MD5 秒传
        // 1. 获取到该文件的MD5
        String md5 = request.getParameter("uniqueIdentifier");
        // 2. 判断该MD5 是否已存在
        boolean contains = md5Set.contains(md5);
        // 3. 已存在直接返回访问路径
        if (contains) {
            return "该文件已上传，链接为：" + "http:xxxxxxxxxxxx";
        } else {
            // 4. 不存在则执行上传并保存MD5 记录到数据库
            OssFile ossFile = minioTemplate.putObject(file.getInputStream(), bucketName, file.getOriginalFilename());
            md5Set.add(md5);
            md5Set.forEach(e -> {
                System.err.println("已存在的MD5：" + e);
            });
            return ossFile;
        }
    }


    @PostMapping("/spring/upload")
    @ResponseBody
    public Object springUpload(@RequestParam MultipartFile file) throws IOException {
        if (file == null || file.isEmpty()) {
            return "文件不能为空";
        }
        try {
            // 1. 创建目标文件夹
            File dir = new File("D:\\tmp" + File.separator + "files");
            if (!dir.exists()) {
                // 文件夹不存在时，创建
                dir.mkdirs();
            }
            // 2. 获取文件信息
            String originalFilename = file.getOriginalFilename();
            // 3. 创建保存文件对象
            String storeFileName = UUID.randomUUID() + "_" + originalFilename; // 文件名采用UUID
            File storeFile = new File(dir.getAbsolutePath() + File.separator + storeFileName);
            file.transferTo(storeFile);
        } catch (Exception e) {
            e.printStackTrace();
            return "上传失败";
        }
        return "success";
    }


    @PostMapping("/common/upload")
    @ResponseBody
    public Object commonUpload(@RequestParam CommonsMultipartFile file) throws IOException {
        if (file == null || file.isEmpty()) {
            return "文件不能为空";
        }
        try {
            // 1. 创建目标文件夹
            File dir = new File("D:\\tmp" + File.separator + "files");
            if (!dir.exists()) {
                // 文件夹不存在时，创建
                dir.mkdirs();
            }
            // 2. 获取文件信息
            String contentType = file.getContentType();
            System.out.println("contentType========" + contentType);
            String name = file.getName();
            System.out.println("name========" + name);
            String originalFilename = file.getOriginalFilename();
            System.out.println("originalFilename========" + originalFilename);
            long size = file.getSize();
            System.out.println("size========" + size);
            Resource resource = file.getResource();
            System.out.println("resource========" + resource.toString());
            String storageDescription = file.getStorageDescription();
            System.out.println("storageDescription========" + storageDescription);
            // 3. 创建保存文件对象
            String storeFileName = UUID.randomUUID() + "_" + originalFilename; // 文件名采用UUID
            File storeFile = new File(dir.getAbsolutePath() + File.separator + storeFileName);
            file.transferTo(storeFile);
        } catch (Exception e) {
            e.printStackTrace();
            return "上传失败";
        }
        return "success";
    }

    @GetMapping
    public Object file() {
        return "file/file";
    }

    @GetMapping("/Uppy")
    public String Uppy() {
        return "Uppy";
    }

    @GetMapping("/FilePond")
    public String FilePond() {
        return "FilePond";
    }


    @PostMapping("/download")
    public ResponseEntity<?> download(@RequestParam String filePath) {
        try (InputStream in = new FileInputStream(filePath)) {
            // 获取文件流
            byte[] body = new byte[in.available()];
            in.read(body); // 写入到字节数组
            String fileName = filePath.substring(filePath.lastIndexOf("\\") + 1); // 截取到文件名
            MultiValueMap<String, String> headers = new HttpHeaders(); // 将文件信息写入消息头
            headers.add("Content-Disposition", "attachment;filename=" + fileName);
            return new ResponseEntity<>(body, headers, HttpStatus.OK);
        } catch (FileNotFoundException exception) {
            exception.printStackTrace();
            return new ResponseEntity<>("服务器FileNotFoundException", HttpStatus.INTERNAL_SERVER_ERROR);

        } catch (IOException e) {
            e.printStackTrace();
            return new ResponseEntity<>("服务器IOException", HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @GetMapping("file2")
    public String file2() {
        return "file2";
    }

    @GetMapping("md5")
    public String md5() {
        return "md5";
    }


    @PostMapping("/uploads")
    @ResponseBody
    public Object uploads(@RequestParam("files") MultipartFile[] files) throws IOException {
        for (MultipartFile file : files) {
            if (file == null || file.isEmpty()) {
                return "文件不能为空";
            }
            try {
                // 1. 创建目标文件夹
                File dir = new File("D:\\tmp" + File.separator + "files");
                if (!dir.exists()) {
                    // 文件夹不存在时，创建
                    dir.mkdirs();
                }
                // 2. 获取文件信息
                String originalFilename = file.getOriginalFilename();
                // 3. 创建保存文件对象
                String storeFileName = UUID.randomUUID() + "_" + originalFilename; // 文件名采用UUID
                File storeFile = new File(dir.getAbsolutePath() + File.separator + storeFileName);
                file.transferTo(storeFile);
            } catch (Exception e) {
                e.printStackTrace();
                return "上传失败";
            }
        }
        return "success";
    }


    @GetMapping("/uploadId")
    @ResponseBody
    public Object uploadId() {
        return "_123456";
    }


    private final static String utf8 = "utf-8";

    @RequestMapping("/uploadMD")
    @ResponseBody
    public void uploadMD(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //分片
        response.setCharacterEncoding(utf8);
        Integer schunk = null;
        Integer schunks = null;
        String name = null;
        String uploadPath = "F:\\fileItem";
        BufferedOutputStream os = null;
        try {
            DiskFileItemFactory factory = new DiskFileItemFactory();
            factory.setSizeThreshold(1024);
            factory.setRepository(new File(uploadPath));
            ServletFileUpload upload = new ServletFileUpload(factory);
            upload.setFileSizeMax(5l * 1024l * 1024l * 1024l);
            upload.setSizeMax(10l * 1024l * 1024l * 1024l);
            List<FileItem> items = upload.parseRequest(request);

            for (FileItem item : items) {
                if (item.isFormField()) {
                    if ("chunk".equals(item.getFieldName())) {
                        schunk = Integer.parseInt(item.getString(utf8));
                    }
                    if ("chunks".equals(item.getFieldName())) {
                        schunks = Integer.parseInt(item.getString(utf8));
                    }
                    if ("name".equals(item.getFieldName())) {
                        name = item.getString(utf8);
                    }
                }
            }
            for (FileItem item : items) {
                if (!item.isFormField()) {
                    String temFileName = name;
                    if (name != null) {
                        if (schunk != null) {
                            temFileName = schunk + "_" + name;
                        }
                        File temFile = new File(uploadPath, temFileName);
                        if (!temFile.exists()) {//断点续传
                            item.write(temFile);
                        }
                    }
                }
            }
            //文件合并
            if (schunk != null && schunk.intValue() == schunks.intValue() - 1) {
                File tempFile = new File(uploadPath, name);
                os = new BufferedOutputStream(new FileOutputStream(tempFile));

                for (int i = 0; i < schunks; i++) {
                    File file = new File(uploadPath, i + "_" + name);
                    while (!file.exists()) {
                        Thread.sleep(100);
                    }
                    byte[] bytes = FileUtils.readFileToByteArray(file);
                    os.write(bytes);
                    os.flush();
                    file.delete();
                }
                os.flush();
            }
            response.getWriter().write("上传成功" + name);
        } finally {
            try {
                if (os != null) {
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 返回分片上传需要的签名数据URL及 uploadId
     *
     * @param bucketName
     * @param fileName
     * @return
     */
    @GetMapping("/createMultipartUpload")
    @SneakyThrows
    @ResponseBody
    public Map<String, Object> createMultipartUpload(String bucketName, String fileName, Integer chunkSize) {
        // 1. 根据文件名创建签名
        Map<String, Object> result = new HashMap<>();
        // 2. 获取uploadId
        String contentType = "application/octet-stream";
        HashMultimap<String, String> headers = HashMultimap.create();
        headers.put("Content-Type", contentType);
        CreateMultipartUploadResponse response = minioTemplate.uploadId(bucketName, null, fileName, headers, null);
        String uploadId = response.result().uploadId();
        result.put("uploadId", uploadId);
        // 3. 请求Minio 服务，获取每个分块带签名的上传URL
        Map<String, String> reqParams = new HashMap<>();
        reqParams.put("uploadId", uploadId);
        List<String> partList = new ArrayList<>();
        // 4. 循环分块数 从1开始,MinIO 存储服务定义分片索引却是从1开始的
        for (int i = 1; i <= chunkSize; i++) {
            reqParams.put("partNumber", String.valueOf(i));
            String uploadUrl = minioTemplate.getPresignedObjectUrl(bucketName, fileName, reqParams);// 获取URL,主要这里前端上传的时候，要传递二进制流，而不是file
            result.put("chunk_" + (i - 1), uploadUrl); // 添加到集合
        }
        return result;
    }


    /**
     * 分片上传完后合并
     *
     * @param objectName 文件全路径名称
     * @param uploadId   返回的uploadId
     * @return /
     */
    @GetMapping("/completeMultipartUpload")
    @SneakyThrows
    @ResponseBody
    public boolean completeMultipartUpload(String bucketName, String objectName, String uploadId) {
        try {
            Part[] parts = new Part[10000];
            // 1. 查询分片
            ListPartsResponse partResult = minioTemplate.listMultipart(bucketName, null, objectName, 10000, 0, uploadId, null, null);
            int partNumber = 1; // 分片序列从1开始
            System.err.println(partResult.result().partList().size() + "========================");
            // 2. 循环获取到的分片信息
            List<Part> partList = partResult.result().partList();
            for (int i = 0; i < partList.size(); i++) {
                // 3. 将分片标记传递给Part对象
                parts[partNumber - 1] = new Part(partNumber, partList.get(i).etag());
                partNumber++;
            }
            minioTemplate.completeMultipartUpload(bucketName, null, objectName, uploadId, parts, null, null);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

        return true;
    }

    @GetMapping("/md5/exists")
    @SneakyThrows
    @ResponseBody
    public boolean exists(String md5) {
        // 数据库中查询该MD5 是否存在
        System.err.println("md5:" + md5);
        return md5Set.contains(md5);
    }

    @GetMapping("/getPutURL")
    @SneakyThrows
    @ResponseBody
    public Object getPutURL() {
        return  minioTemplate.getPresignedObjectUrl("pearl-buckent","mmexport1646729143581.jpg",null);
    }


    private Set<String> md5Set = new HashSet<>();

}
